package com.weilus.filters;

import lombok.Data;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.PathContainer;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * spring:
 *   cloud:
 *     gateway:
 *       limiter:
 *       - id: flux-call
 *         limits:
 *         - GET /test/**=5/s           #5次每秒
 *         - /test/other=100/s          #100次每秒
 */
@Data
@RefreshScope
@Configuration
@ConfigurationProperties(prefix = "spring.cloud.gateway.limiter")
public class LimiterProperties extends ArrayList<LimiterProperties.RouterLimiter> implements InitializingBean {
    private Map<String,Set<LimiterMatcher>> authMatchers = new HashMap<>();
    @Autowired
    private ApplicationContext context;


    public Set<RedisRateLimiter> getMatchers(String routeId, HttpMethod method, PathContainer path){
        Set<LimiterMatcher> matchers = authMatchers.get(routeId);
        if (CollectionUtils.isEmpty(matchers)){
            return Collections.EMPTY_SET;
        }
        return matchers.stream()
                .filter(m -> m.getPathPattern().matches(path) && (m.getMethod() == null || m.getMethod() == method))
                .map(m->{
                    RedisRateLimiter limiter = new RedisRateLimiter(m.getLimit(), m.getLimit());
                    limiter.setApplicationContext(context);
                    return limiter;
                })
                .collect(Collectors.toSet());
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if(!CollectionUtils.isEmpty(this)){
            this.authMatchers = this.stream().collect(Collectors.toMap(RouterLimiter::getRouteId,t->
                    t.getLimits().stream().map(LimiterMatcher::of).collect(Collectors.toSet())
            ));
        }
    }

    @Data
    public static class RouterLimiter{
        private String routeId;
        private List<String> limits;
    }

    @Data
    public static class LimiterMatcher {
        public final static Pattern timesPattern = Pattern.compile("^(\\d{1,}?)/[s,m,h,d]$");
        public final static PathPatternParser pathPatternParser = new PathPatternParser();

        private PathPattern pathPattern;
        private HttpMethod method;
        private int limit;

        public static LimiterMatcher of(String config) {
            String[] arr = StringUtils.delimitedListToStringArray(config, "=");
            String pathConfig = arr[0], authConfig = arr[1];
            String[] pathArr = StringUtils.delimitedListToStringArray(pathConfig, " ");
            LimiterMatcher matcher = new LimiterMatcher();
            matcher.setPathPattern(pathPatternParser.parse(pathArr.length == 1 ? pathArr[0] : pathArr[1]));
            matcher.setMethod(HttpMethod.resolve(pathArr.length == 1 ? null : pathArr[0]));
            Matcher timesMatcher = timesPattern.matcher(authConfig);
            if (timesMatcher.find()) {
                matcher.setLimit(Integer.valueOf(timesMatcher.group(1)));
            }
            return matcher;
        }

        @Override
        public String toString() {
            return "{" +
                    "method=" + method + ",pathPattern=" + pathPattern + ", limit=" + limit +
                    '}';
        }
    }
}
